/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.weather;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.TimeZone;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;

/**
 *
 * @author tangd
 */
public class Wunderground implements WeatherFetcher {
    Document cityPage;
    boolean _useMetric;
    public Wunderground(boolean useMetric) { _useMetric = useMetric; }
    public Document getXML(String url) {
        try {
          Document xmlFeed = Jsoup.connect(url).get();
          return xmlFeed;
        }
        catch(IOException ioEx) {
            System.out.println("Error fetching XML document: " + url);
        }
        return null;
    }
    //<editor-fold desc="Fetch information about the searched location" defaultstate="collapsed">
    public ArrayList<String> searchCities(String search) {
        ArrayList<String> cities = new ArrayList<String>();
        cityPage = getXML("http://api.wunderground.com/auto/wui/geo/GeoLookupXML/index.xml?query="+search);
        Elements matches = cityPage.select("location");
        if(matches.size()>1) {
            for(int i = 0; i<matches.size(); i++) {
                cities.add(cityPage.select("location name").get(i).ownText());
            }
        }
        else if(matches.size()==1)
            cities.add(matches.select("city").get(0).ownText());
        return cities;
    }
    public String getCityString(ArrayList<String> matchedLocations, String search) {
        String outputString = "";
        if(matchedLocations.size()>1) {
            outputString = "Multiple results found: ";
            if(matchedLocations.size()>5) {
                outputString = outputString + matchedLocations.get(0);
                for(int i = 1; i<5; i++) {
                    outputString = outputString + "; "+ matchedLocations.get(i);
                }
                outputString = outputString + "; 5 of " +matchedLocations.size() + " results shown";
            }
            else {
                outputString = outputString + matchedLocations.get(0);
                for(int i = 0; i < matchedLocations.size(); i++) {
                    outputString = outputString + ", " + matchedLocations.get(i);
                }
            }
        }
        else if(matchedLocations.size()==0)
            outputString = "No result found for: "+search;
        return outputString;
    }
    //</editor-fold>
    //<editor-fold desc="Get forecast information" defaultstate="collapsed">
    public ForecastData getForecastData(String search) {
        Document forecastPage = getXML("http://api.wunderground.com/auto/wui/geo/ForecastXML/index.xml?query="+search);
        return new ForecastData(forecastPage.select("simpleforecast"));
    }
    public String getForecast(String search) {
        ArrayList<String> matchedLocations = searchCities(search);
        String outputString = getCityString(matchedLocations, search);
        if(outputString.length()!=0)
            return outputString;
        else
        {
            CityData city = new CityData(cityPage);
            ForecastData data = getForecastData(search);
            data.parseXML();
            outputString = String.format("%s, %s Forecast  (high/low); ", city.getCity(), city.getState());
            for(int i=0;i<data.forecastLength(); i++) {
                ForecastDay day = data.getDay(i);
                String dayString = String.format("%s:  %s, %s (%s), %d%% rain; ", day.getDay(),day.getCondition(), day.getHighLowString(_useMetric), day.getHighLowString(!_useMetric), day.getRain());
                outputString = outputString + dayString;
            }
            return outputString;
        }
    }
    //</editor-fold>
    //<editor-fold desc="Get current weather string" defaultstate="collapsed">
    public String getWeather(String search) {
        ArrayList<String> matchedLocations = searchCities(search);
        String outputString = getCityString(matchedLocations, search);
        if(outputString.length()!=0)  {
            return outputString;
        }
        else
        {
            CityData city = new CityData(cityPage);
            ForecastData forecastData = getForecastData(search);
            forecastData.parseXML();
            ForecastDay forecast = forecastData.getDay(0);
            Document weatherXML = getXML("http://api.wunderground.com/auto/wui/geo/WXCurrentObXML/index.xml?query="+search);
            WeatherData weather = new WeatherData(weatherXML.select("current_observation"),forecast, city);
            try {
            outputString = String.format("%s, %s (%s/%s); Updated: %s; Conditions: %s; Temperature: %s (%s); Humidity: %s; High/Low: %s (%s); Wind: %s;"
                    , city.getCity(), city.getState(), city.getCoordinates()[0], city.getCoordinates()[1], weather.getTimeString(), weather.getConditions(),
                    weather.getTemp(_useMetric), weather.getTemp(!_useMetric),weather.getHumidity(),forecast.getHighLowString(_useMetric), forecast.getHighLowString(!_useMetric),
                    weather.getWindString(_useMetric));
            }
            catch(Exception e) {
                System.out.println(e.getMessage());
            }
            return outputString;
        }
    }
    //</editor-fold>
    private class CityData {
        private String _city, _country, _state, _timezone;
        private String _latitude, _longitude;
        public CityData(String city, String country, String state, String timezone, String latitude, String longitude) {
            _city = city;
            _country = country;
            _state = state;
            _timezone = timezone;
            double lat = Double.parseDouble(latitude);
            if (lat < 0) {
                _latitude = String.format("%.1f°S", Math.abs(lat));
            } else {
                _latitude = String.format("%.1f°N", lat);
            }
            double longi = Double.parseDouble(longitude);
            if (longi < 0) {
                _longitude = String.format("%.1f°W", Math.abs(longi));
            } else {
                _longitude = String.format("%.1f°E", Math.abs(longi));
            }
        }

        public CityData(Document cityPage) {
            _state = cityPage.select("location state").get(0).ownText();
            _country = cityPage.select("location country").get(0).ownText();
            _city = cityPage.select("location city").get(0).ownText();
            _timezone = cityPage.select("location tz_unix").get(0).ownText();
            String latitude = cityPage.select("location lat").first().ownText();
            String longitude = cityPage.select("location lon").first().ownText();
            double lat = Double.parseDouble(latitude);
            if (lat < 0) {
                _latitude = String.format("%.1f°S", Math.abs(lat));
            } else {
                _latitude = String.format("%.1f°N", lat);
            }
            double longi = Double.parseDouble(longitude);
            if (longi < 0) {
                _longitude = String.format("%.1f°W", Math.abs(longi));
            } else {
                _longitude = String.format("%1f°E", Math.abs(longi));
            }
        }

        public String getCity() {
            return _city;
        }

        public String getCountry() {
            return _country;
        }

        public String getState() {
            return _state;
        }

        public String getTimeZone() {
            return _timezone;
        }

        public String[] getCoordinates() {
            String[] coordinates = new String[2];
            coordinates[0] = _longitude;
            coordinates[1] = _latitude;
            return coordinates;
        }
    }
    private class ForecastData {

        Elements _simpleForecast;
        private ArrayList<ForecastDay> forecast = new ArrayList<ForecastDay>();

        public ForecastData(Elements simpleForecast) {
            _simpleForecast = simpleForecast;
        }

        public void parseXML() {
            Elements forecastDays = _simpleForecast.select("forecastday");
            for (int i = 0; i < 5 && i < forecastDays.size(); i++) {
                Elements foreCastDay = forecastDays.eq(i);
                int highF = Integer.parseInt(foreCastDay.select("high fahrenheit").first().ownText());
                int highC = Integer.parseInt(foreCastDay.select("high celsius").first().ownText());
                int lowF = Integer.parseInt(foreCastDay.select("low fahrenheit").first().ownText());
                int lowC = Integer.parseInt(foreCastDay.select("low celsius").first().ownText());
                String day = foreCastDay.select("date weekday").first().ownText();
                String condition = foreCastDay.select("conditions").first().ownText();
                int rain = Integer.parseInt(foreCastDay.select("pop").first().ownText());
                ForecastDay dayRecord = new ForecastDay(highC, highF, lowC, lowF, day, condition, rain);
                forecast.add(dayRecord);
            }
        }

        public ForecastDay getDay(int i) {
            if (i < 5 && i < forecast.size()) {
                return forecast.get(i);
            }
            return null;
        }

        public int forecastLength() {
            return forecast.size();
        }
    }
    private class ForecastDay {

        private int _highC, _highF;
        private int _lowC, _lowF;
        private String _day;
        private String _condition;
        private int _rainChance;

        public ForecastDay(int highC, int highF, int lowC, int lowF, String day, String condition, int rainChance) {
            _highC = highC;
            _highF = highF;
            _lowC = lowC;
            _lowF = lowF;
            _day = day;
            _condition = condition;
            _rainChance = rainChance;
        }

        public int getHigh(boolean metric) {
            return metric ? _highC : _highF;
        }

        public int getLow(boolean metric) {
            return metric ? _lowC : _lowF;
        }

        public String getHighLowString(boolean metric) {
            if (metric) {
                return _highC + "/" + _lowC + "°C";
            } else {
                return _highF + "/" + _lowF + "°F";
            }
        }

        public String getDay() {
            return _day;
        }

        public String getCondition() {
            return _condition;
        }

        public int getRain() {
            return _rainChance;
        }
    }
    private class WeatherData {

        private long _observedTime;
        private int _tempF, _tempC, _windSpeed;
        private String _windDirection;
        private String _conditions, _humidity;
        private SimpleDateFormat dateFormatter = new SimpleDateFormat("h:m a zzz (MMM d, yyyy)");
        private CityData _city;
        private ForecastDay _forecast;

        public WeatherData(long observedTime, int tempF, int tempC, String windDirection, int windSpeed, String conditions, CityData city, ForecastDay forecast, String humidity) {
            _observedTime = observedTime * 1000;
            _tempF = tempF;
            _tempC = tempC;
            _windDirection = windDirection;
            _windSpeed = windSpeed;
            _conditions = conditions;
            _humidity = humidity;
            _city = city;
            _forecast = forecast;
        }

        public WeatherData(Elements weatherXML, ForecastDay forecast, CityData city) {
            _observedTime = Long.parseLong(weatherXML.select("observation_epoch").first().ownText()) * 1000;
            _tempF = Integer.parseInt(weatherXML.select("temp_f").first().ownText());
            _tempC = Integer.parseInt(weatherXML.select("temp_c").first().ownText());
            _windDirection = weatherXML.select("wind_dir").first().ownText();
            _conditions = weatherXML.select("weather").first().ownText();
            _humidity = weatherXML.select("relative_humidity").first().ownText();
            _windSpeed = Integer.parseInt(weatherXML.select("wind_mph").first().ownText());
            _city = city;
            _forecast = forecast;
        }

        public String getTimeString() {
            Date d = new Date(_observedTime);
            dateFormatter.setTimeZone(TimeZone.getTimeZone(_city.getTimeZone()));
//        System.out.println("timezone: "+_city.getTimeZone());
            return dateFormatter.format(d);
        }

        public String getTemp(boolean metric) {
            return metric ? _tempC + "°C" : _tempF + "°F";
        }

        public String getWindDirection() {
            return _windDirection;
        }

        public String getWindSpeed(boolean metric) {
            return metric ? Math.round(_windSpeed * 1.609f) + "kph" : _windSpeed + "mph";
        }

        public String getWindString(boolean metricFirst) {
            if (_windSpeed == 0) {
                return "Calm";
            }
            String output = getWindDirection() + " at ";
            output = output + getWindSpeed(metricFirst) + " (" + getWindSpeed(!metricFirst) + ")";
            return output;
        }

        public String getConditions() {
            return _conditions;
        }

        public String getHumidity() {
            return _humidity;
        }
    }
}
